/* $RCSFile: ErrorHandler.cs $
 * Apache license v2 2011 by Christian Surieux
 * $Source: ?/ADNCRM.Core/Configuration/Exceptions/ErrorHandler.cs $
 * $log$
 * Revision 6 2010/08/06 19:56:27  christian.surieux
 *   Added Log to All cs files
 */

using System;
using System.Diagnostics;
using System.Threading;
using System.Net;
using System.ComponentModel;
using System.Collections;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Text;
using System.IO;
using System.Web;
using System.Data.SqlClient;
using System.Configuration;
using System.Web.Configuration;
using System.Globalization;

using AddonNice.Diagnostics;
using AddonNice.Configuration;
using AddonNice.Helpers;
using AddonNice.Settings;
using AddonNice.Settings.Cache;
using AddonNice.UI;
using AddonNice.CRMWrapper;

namespace AddonNice.Configuration
{
	/// <summary>
	/// This class in combination with the Web.Config file handles all the Errors that are not caught programatically
	/// 99% of the time Errors will be caught by HttpUrlModule, this class will be called, errors will be 
	/// logged depending on what was specified by the Web.Config file, after that the error cascades up and is caught
	/// by the customErrors settings in Web.Config. 
	/// Here you can specify errors and which pages to redirect to.
	/// Visitors will be directed to a dynamic aspx page for General Errors and 404 Errors (Specified aspx page does not exist)
	/// If these dynamic pages themselves have an error (e.g the DatabaseDB has crashed 
	/// so it can't retrieve the theme or translations, then there is code in these pages to catch errors at the
	/// Page Level and redirect to a static html page (one for general errors and one for 404 errors). 
	/// These pages will have no theme at all, just text (So that they will work across multiple themes) and the 
	/// text will be in English (No Translation - Although multiple versions of the html pages could be created to
	/// handle this. 
	/// 
	/// </summary>
	public class ErrorHandler
	{
        /// <summary>
        /// Crm sends the id query string with {} surounding the guid value, not AddonNice
        /// We use this to differenciate internal and external calls
        /// </summary>
        public static bool IsCrmEntityID
        {
            get
            {
                if ( HttpContext.Current != null)
                {
                    string obj = HttpContext.Current.Request[CrmStr.strCrmID];
                    if (!string.IsNullOrEmpty(obj))
                        return (obj.IndexOf('{') > -1 );
                    return false;
                }
                return false;
            }
        }

        /// <summary>
        /// When in creation mode we have no entityID, this is the reason why
        /// We add a Crm marker in the iframe url /site/ucrm_1/PageId/xxx.aspx
        /// </summary>
        public static bool HasCrmMark
        {
            get
            {
                if ( HttpContext.Current != null)
                {
                    string obj = HttpContext.Current.Request[CrmStr.strCrmMark];
                    if (!string.IsNullOrEmpty(obj))
                        return true;
                    return false;
                }
                return false;
            }
        }
		/// <summary>
		/// Used to check if running in a CRM iFrame
		/// </summary>
		public static bool IsUnderCRM
		{
			get
			{
				string str = HttpContext.Current.Request[CrmStr.strCrmTypeName];
                bool under = ( HasCrmMark || (!string.IsNullOrEmpty(str) && IsCrmEntityID ));
				if (str != null)
					return true;
				return false;
			}
		}


        public static ADNPage CurrentADNPage()
        {
            if (( HttpContext.Current != null) && HttpContext.Current.Items.Contains("ADNPage") )
            {
                return (ADNPage)HttpContext.Current.Items["ADNPage"];
            }
            return null;
        }


        /// <summary>
        /// Called only by Application_Error in global.asax.cs to deal with unhandled exceptions. 
        /// </summary>
        public static void ProcessUnhandledException()
        {
            bool TraceError                     =   SmartErrorTraceSwitch.Sw.TraceError;
			bool existContext                   =   ( HttpContext.Current != null );
            object objptl                       =   (existContext) ? HttpContext.Current.Items[StStr.strPortalSettings]:null;
			bool existPS                        =   ( objptl != null );
			if ( TraceError )
            {
                Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException BEG portalSettings exists: {0}", existPS),SmartErrorTraceSwitch.Sw.Info);
			    if ( existPS)
                {
                    PortalSettings ps           =   objptl as PortalSettings;
                    if ( ps.ActivePageSettings != null )
                        Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException ActivePageSettings.PageID: {0}", ps.ActivePageSettings.PageID),SmartErrorTraceSwitch.Sw.Info);

                }
                if ( existContext )
                {
                    HttpRequest Request         =   HttpContext.Current.Request;
                    Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException rawUrl: {0}, UserLanguages.Len: {1}, UserLanguages: {2}.", 
                        Request.RawUrl,Request.UserLanguages.Length,(Request.UserLanguages.Length > 0) ? Request.UserLanguages[0]:string.Empty),SmartErrorTraceSwitch.Sw.Info);
                    Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException Crawler: {0}, IsMobileDevice: {1}, UserAgent: '{2}', UserHostAddress: {3}", 
                        new object[]{Request.Browser.Crawler,Request.Browser.IsMobileDevice,Request.UserAgent,Request.UserHostAddress}),SmartErrorTraceSwitch.Sw.Info);
                }
            }
			try
			{
				Exception e			            =   HttpContext.Current.Server.GetLastError();
				if ( TraceError )
                    Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException enter  GetLastError: {0}", e),SmartErrorTraceSwitch.Sw.Info);
				while (e is HttpException && e.InnerException != null)
				{
					e                           =   e.InnerException;
					if ( TraceError )
                        Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException enter  InnerException: {0}", e),SmartErrorTraceSwitch.Sw.Info);
				}

				HttpResponse resp				=   HttpContext.Current.Response;
				// We don't want to send any header before final processing
				try
				{
					resp.Clear();
					resp.BufferOutput           =   true;
				}
				catch(Exception exA)
				{
					if ( TraceError )
                        Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException error clearing response Ex: {0}", exA),SmartErrorTraceSwitch.Sw.Info);
				}

                // don't go back to Application_Error in global.asax
                HttpContext.Current.Server.ClearError();

                string _redirectUrl				=   Config.SmartErrorRedirect; // default value
				InternalLogLevel _logLevel		=   InternalLogLevel.Fatal; // default value
				HttpStatusCode _httpStatusCode	=   HttpStatusCode.InternalServerError; // default value
				string myCacheKey				=   string.Empty;
				HttpRequest req					=   HttpContext.Current.Request;
				bool rOk						=   ( req != null);
				if ( TraceError )
                    Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException request Ok:{0}, AbsolutePath: '{1}'", rOk, (rOk) ? req.Url.AbsolutePath : string.Empty),SmartErrorTraceSwitch.Sw.Info);
				if ( rOk && req.Url.AbsolutePath.EndsWith(Config.SmartErrorRedirect.Substring(2)))
				{
					if ( TraceError )
                        Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException looping error condition AbsolutePath.EndsWith: {0}", Config.SmartErrorRedirect.Substring(2)),SmartErrorTraceSwitch.Sw.Info);
					resp.Write(LocalizeHelper.Localize("ADNCRM_CRITICAL_ERROR", "Sorry - a critical error has occurred - unable to continue"));
					resp.StatusCode             =   (int)HttpStatusCode.ServiceUnavailable;
					resp.Cache.SetCacheability(HttpCacheability.NoCache);
					resp.End();
					return; // never goes there as End send ThreadAbortException
				}

				bool responseWritten			=   false;
                bool shouldLogInCache           =   true;
				try
				{
					if ( TraceError )
                        Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException Exception:{0}", e.GetType().FullName),SmartErrorTraceSwitch.Sw.Info);
					if ( e is CookieLessException )
					{
						_logLevel			    =   InternalLogLevel.Fatal;
						_redirectUrl		    =   Config.CookieLessErrorRedirect;
					}
                    else if (e is CRMWrapper.CRMWrapperException)
					{
						_logLevel			    =   InternalLogLevel.Fatal;
						_redirectUrl		    =   Config.CRMErrorRedirect;
						_httpStatusCode		    =   Config.CRMErrorResponse;
					}
					else if (e is LicenseException || e is ADNCRMNoLicense)
					{
						_logLevel			    =   InternalLogLevel.Fatal;
						_redirectUrl		    =   Config.NoLicenseErrorRedirect;
						_httpStatusCode		    =   Config.NoLicenseErrorResponse;
					}
					else if (e is DatabaseUnreachableException || e is SqlException)
					{
						_logLevel			    =   InternalLogLevel.Fatal;
						_redirectUrl		    =   Config.DatabaseErrorRedirect;
						_httpStatusCode		    =   Config.DatabaseErrorResponse;
					}
					else if (e is DatabaseVersionException) // db version is behind code version
					{
						_logLevel			    =   InternalLogLevel.Fatal;
						_httpStatusCode		    =   Config.DatabaseUpdateResponse;
						_redirectUrl		    =   Config.DatabaseUpdateRedirect;
                        shouldLogInCache        =   false;
					}
					else if (e is CodeVersionException) // code version is behind db version
					{
						_logLevel			    =   InternalLogLevel.Fatal;
						_httpStatusCode		    =   Config.CodeUpdateResponse;
						_redirectUrl		    =   Config.CodeUpdateRedirect;
					}
					else if (e is PortalsLockedException) // AllPortals lock is "on"
					{
						_logLevel			    =   ((PortalsLockedException)e).Level;
						_httpStatusCode		    =   ((PortalsLockedException)e).StatusCode;
						_redirectUrl		    =   Config.LockRedirect;
						e					    =   null;
					}
					else if (e is ADNCRMRedirect)
					{
						_logLevel			    =   ((ADNCRMRedirect)e).Level;
						_httpStatusCode		    =   ((ADNCRMRedirect)e).StatusCode;
						_redirectUrl		    =   ((ADNCRMRedirect)e).RedirectUrl;
                        shouldLogInCache        =   false;
					}
					else if (e is ADNCRMException)
					{
						_logLevel			    =   ((ADNCRMException)e).Level;
						_httpStatusCode		    =   ((ADNCRMException)e).StatusCode;
						_redirectUrl		    =   ((ADNCRMException)e).RedirectUrl;
					}
					else if (e is HttpException)
					{
						_logLevel			    =   InternalLogLevel.Fatal;
						_httpStatusCode		    =   (HttpStatusCode)((HttpException)e).GetHttpCode();
					}
					else
					{
						_logLevel			    =   InternalLogLevel.Fatal; // default value
						_httpStatusCode		    =   HttpStatusCode.InternalServerError; // default value
					}

					if ( TraceError )
                        Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException _logLevel:{0}, _httpStatusCode:{1}, _redirectUrl: '{2}'", _logLevel, _httpStatusCode, _redirectUrl),SmartErrorTraceSwitch.Sw.Info);
                    if ( shouldLogInCache )
                    {

                        // create unique id
                        string _myGuid	        =   Guid.NewGuid().ToString("N");
                        string _auxMessage		=   string.Format("errorGUID: {0}",_myGuid);

                        // log it
                        StringWriter sw			=   new StringWriter();
                        writeToStream(_logLevel,_auxMessage,e,sw);

                        // bundle the info
                        ArrayList storedError	=   new ArrayList(3);
                        storedError.Add(_logLevel);
                        storedError.Add(_myGuid);
                        storedError.Add(sw.ToString());
                        // cache it
                        StringBuilder sb		=   new StringBuilder(Portal.UniqueID);
                        sb.AppendFormat("_adn_error_{0}",_myGuid);
                        myCacheKey              =   sb.ToString();
                        CurrentCache.Insert0(myCacheKey,storedError);
                        if ( TraceError )
                            Trace.WriteLine(string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException Error inserted in cach Key:{0}",myCacheKey),SmartErrorTraceSwitch.Sw.Info);
                    }
				}
				catch (Exception ex2)
				{
					if ( TraceError )
                        Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException ex2:{0}, _redirectUrl:{1}", ex2, _redirectUrl),SmartErrorTraceSwitch.Sw.Info);
					try
					{
						responseWritten         =   true;
						resp.WriteFile(Config.CriticalErrorRedirect);
						resp.StatusCode         =   (int)Config.CriticalErrorResponse;
						resp.Cache.SetCacheability(HttpCacheability.NoCache);
						resp.End();
					}
					catch (ThreadAbortException)
					{
                        return;
					}
					catch(Exception ex5)
					{
						if ( TraceError )
                            Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException ex5:{0}", ex5),SmartErrorTraceSwitch.Sw.Info);
						responseWritten         =   true;
						resp.Write(LocalizeHelper.Localize("ADNCRM_CRITICAL_ERROR", "Sorry - a critical error has occurred - unable to continue"));
						resp.StatusCode         =   (int)Config.CriticalErrorResponse;
						resp.Cache.SetCacheability(HttpCacheability.NoCache);
						resp.End();
					}
				}
				finally
				{
					if ( TraceError )
                        Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException finally _redirectUrl :{0}, responseWritten: {1}", _redirectUrl,responseWritten),SmartErrorTraceSwitch.Sw.Info);
					if (!responseWritten)
					{
						if (_redirectUrl.StartsWith("http://")	|| ( e != null && (e is DatabaseVersionException) ) )
						{
							if ( TraceError )
                                Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException Redirecting1 to _redirectUrl:{0}", _redirectUrl),SmartErrorTraceSwitch.Sw.Info);
						}
						else if ( _redirectUrl.IndexOf(".aspx") > 0)
						{
							// append params to redirect url if on same server
							StringBuilder sb	    =   new StringBuilder();
							if (_redirectUrl.IndexOf("?") != -1)
							{
								sb.Append(_redirectUrl.Substring(0, _redirectUrl.IndexOf("?") + 1));
								sb.Append(((int)_httpStatusCode).ToString());
								sb.Append("&eid=");
								sb.Append(myCacheKey);
                                if ( IsUnderCRM )
                                {
								    sb.Append("&undercrm=1");
                                }
								sb.Append("&");
								sb.Append(_redirectUrl.Substring(_redirectUrl.IndexOf("?") + 1));
							}
							else
							{
								sb.Append(_redirectUrl);
								sb.Append("?");
								sb.Append(((int)_httpStatusCode).ToString());
								sb.Append("&eid=");
                                if ( IsUnderCRM )
                                {
								    sb.Append("&undercrm=1");
                                }
								sb.Append(myCacheKey);
							}
							_redirectUrl            =   sb.ToString();
                        }
						if ( TraceError )
                            Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException Redirecting to _redirectUrl: '{0}'", _redirectUrl),SmartErrorTraceSwitch.Sw.Info);
						//resp.Clear();
                        ADNPage Pg                  =   CurrentADNPage();
                        if ( ( Pg != null ) && Pg.IsInAsyncPostBack && ( Pg.AjaxManager != null ) )
                        {
                            try
                            {
                                Pg.Redirect(_redirectUrl,true);
                            }
                            catch
                            {
                                resp.Redirect(_redirectUrl,true); // Pb with Telerik AjacRedirect trying to access unvailable Page.Response ??
                            }
                        }
                        else    resp.Redirect(_redirectUrl,true);
			        }
				}
			}
			catch (ThreadAbortException)
			{
				if ( TraceError )
                    Trace.WriteLine("[E]ErrorHandler ProcessUnhandledException ThreadAbortException after redirectUrl.",SmartErrorTraceSwitch.Sw.Info);
				return;
			}
			catch (Exception ex3)
			{
				if ( TraceError )
                    Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"[E]ErrorHandler ProcessUnhandledException Unexpected error, ex3 : {0}", ex3),SmartErrorTraceSwitch.Sw.Info);
                throw;
			}
        }

        /// <summary>
        /// Trace the exception + stack in a memorystream to push it in cache
        /// </summary>
        /// <param name="_logLevel">AddonNice.Configuration.LogLevel enumerator</param>
        /// <param name="_auxMessage">Text message to be shown in log entry</param>
        /// <param name="e">Exception object (can be null)</param>
        /// <param name="sw">A StringWriter object which will be filled with a formatted version of the log entry</param>
        private static void writeToStream(InternalLogLevel _logLevel, string _auxMessage, Exception e, StringWriter sw)
        {
            string str  =   string.Format("ErrorHandler LogLevel: {0} ,Error: {1}, ex : {2}<br />", _logLevel, _auxMessage, (e == null ) ? "null":e.Message);
            sw.WriteLine(str);
			if ( e == null )
				return;
			str         =   string.Format("ErrorHandler StackTrace :<br /> {0}",e.StackTrace);
			sw.WriteLine(str);
 			while (e.InnerException != null)
			{
				e       =   e.InnerException;
				str     =   string.Format("ErrorHandler InnerException ex : {0}<br />",e);
				sw.WriteLine(str);
			}
       }
	}
}